Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation generation functionality #367

Merged
merged 13 commits into from
Mar 21, 2020
Merged

Add documentation generation functionality #367

merged 13 commits into from
Mar 21, 2020

Conversation

iann0036
Copy link
Contributor

Looking for feedback on functionality to add documentation generation during a cfn generate. Will generate only initial README.md without correct types for now.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@rjlohan
Copy link
Contributor

rjlohan commented Dec 13, 2019

Tried running this branch on one of my local repos but hit a bug;

Traceback (most recent call last):
  File "/Users/ryanloha/workspaces/cloudformation-cli/src/rpdk/core/cli.py", line 98, in main
    args.command(args)
  File "/Users/ryanloha/workspaces/cloudformation-cli/src/rpdk/core/generate.py", line 16, in generate
    project.generate_docs()
  File "/Users/ryanloha/workspaces/cloudformation-cli/src/rpdk/core/project.py", line 317, in generate_docs
    contents = template.render(type_name=self.type_name, schema=self.schema)
  File "/Users/ryanloha/workspaces/cloudformation-cli/env/lib/python3.7/site-packages/jinja2/asyncsupport.py", line 76, in render
    return original_render(self, *args, **kwargs)
  File "/Users/ryanloha/workspaces/cloudformation-cli/env/lib/python3.7/site-packages/jinja2/environment.py", line 1008, in render
    return self.environment.handle_exception(exc_info, True)
  File "/Users/ryanloha/workspaces/cloudformation-cli/env/lib/python3.7/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/ryanloha/workspaces/cloudformation-cli/env/lib/python3.7/site-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "/Users/ryanloha/workspaces/cloudformation-cli/src/rpdk/core/templates/docs-readme.yml", line 63, in top-level template code
    {% if len(identifierptr) == 1 %}
jinja2.exceptions.UndefinedError: 'len' is undefined

This is running against the schema from this PR: aws-cloudformation/aws-cloudformation-resource-providers-cloudformation#4

It's possible that my schema is broken as I haven't finished that, but at the least we probably want to be more tolerant to bad schemas in this doc generation. I think it's a valid schema though, as validation should happen as part of cfn generate before it gets to this new doc generation path.

@iann0036
Copy link
Contributor Author

TIL you can use composite keys for the primaryIdentifier...will cater for that.

Working on the sub-property pages and type detection now.

@iann0036 iann0036 marked this pull request as ready for review December 14, 2019 10:36
@iann0036
Copy link
Contributor Author

@rjlohan Docs generation is now complete. Seeking full review.

Copy link
Contributor

@tobywf tobywf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! will take a look fully next week. there's a bunch of stuff around using pathlib that would be great to do. although our CI doesn't build Windows yet, would be great to keep it OS agnostic.

src/rpdk/core/project.py Outdated Show resolved Hide resolved
src/rpdk/core/project.py Outdated Show resolved Hide resolved
src/rpdk/core/project.py Outdated Show resolved Hide resolved
src/rpdk/core/project.py Show resolved Hide resolved
src/rpdk/core/project.py Outdated Show resolved Hide resolved
tests/test_project.py Outdated Show resolved Hide resolved
tests/test_project.py Outdated Show resolved Hide resolved
tests/test_project.py Outdated Show resolved Hide resolved
@iann0036
Copy link
Contributor Author

Thanks @tobywf!

Is there a cleaner way to do this? Looked at JsonSchemaFlattener and it seemed close but not quite what I needed to resolve $ref's.

@tobywf
Copy link
Contributor

tobywf commented Dec 16, 2019

if i'm reading it right, i think John did something similar in #315 with RefResolver from jsonschema:

def resolve_ref(self, schema):
return self.resolver.resolve(schema["$ref"])[1]

maybe can use a similar approach? (it isn't ideal since it's an internal class - but we haven't gotten around to write our own, and something already uses it, so fine for now.)

@iann0036
Copy link
Contributor Author

if i'm reading it right, i think John did something similar in #315 with RefResolver from jsonschema:

That RefResolver class was perfect! Replaced in 1a31248.

Copy link
Contributor

@tobywf tobywf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do love this feature, but one of my concerns is this feature is opted in by default. I's complex, and simply doesn't work reliably yet when running against real schemas, e.g.:

  File "aws-cloudformation-rpdk/src/rpdk/core/templates/docs-readme.yml", line 55, in top-level template code
    _Allowed Values_: <code>{{ "</code> | <code>".join(prop.allowedvalues) }}</code>
TypeError: sequence item 0: expected str instance, int found

I also get weird doubling in the sub-property page titles "AWS::Foo::Bar Spam Spam Eggs Eggs" (again, real schema). I'll spend some more time and see if I can debug some of these issues. Do you mind me pushing new commits to this PR, or would you prefer I branch off?

tests/test_project.py Outdated Show resolved Hide resolved
@iann0036
Copy link
Contributor Author

@tobywf Fine for you to make direct edits.

Also happy for the docs generation to be opt-in (cfn generate flag?).

From memory, when generating sub-property pages I had it generate AWS::Foo::Bar Spam Eggs format (and not just the last element) in order to avoid having two pages of the exact same title - I believe the AWS:: types always have explicit names for these definitions, but this may not be the case in other schemas. Agree the double up is a bug though.

@tobywf
Copy link
Contributor

tobywf commented Jan 22, 2020

I'm just going to run it against a bunch of schemas and see what happens. If it works reliably, no need to make it opt-in.

@tobywf
Copy link
Contributor

tobywf commented Jan 23, 2020

Okay, fixed the issue with different data types, debugging the duplication. It happens with arrays:

propname: Foos, proppath: ['Foos'] | prop: {'type': 'array', 'items': {'$ref': '#/definitions/Foo'}}
elif prop["type"] == "array":
    prop["arrayitems"] = self._set_docs_properties(
        propname, prop["items"], proppath
    )
propname: Foos, proppath: ['Foos', 'Foos'] | prop: {'$ref': '#/definitions/Foo'}

An idea is using "Items" as propname? For AccessAnalyzer, that results in:

archiverules-items-filter-items.md
archiverules-items.md
tags-items.md

But not doing the duplication is what I've gone with for now.

@iann0036
Copy link
Contributor Author

If you appended -items- to those you'd have to do similar for actual properties (e.g. someobj-properties-key.md) so there's no issue when the actual key Items: exists.

* Avoid `.join()` in templates; it expects strings only
* Use existing functions to decode JSON pointers
* Add `.md` to Jinja autoescape settings to prevent HTML injection from e.g. schema descriptions
* Also rename templates to correct file-ending of resulting files
* Avoid modifying dictionaries in a loop where possible
* Check for nested primaryIdentifier
* Support nested additionalIdentifiers (but not compound ones)
* Make proppath a tuple, so it is immutable
* Avoid modifying proppath for type "array" to avoid duplicate propname in proppath
* Liberal use of f-strings
@tobywf
Copy link
Contributor

tobywf commented Jan 23, 2020

awesome, I tried it against a bunch of schemas, and it works great. got a bit carried away, we have functions to parse the JSON pointers. i think we'll try and get this out ASAP in the next release.

@tobywf tobywf requested a review from johnttompkins January 23, 2020 01:22
src/rpdk/core/project.py Show resolved Hide resolved
src/rpdk/core/project.py Outdated Show resolved Hide resolved
@tobywf
Copy link
Contributor

tobywf commented Jan 24, 2020

Found another issue: read-only properties should really be excluded, since they can't be specified.

@PatMyron
Copy link
Contributor

PatMyron commented Feb 24, 2020

It could be interesting to include regional availability information here eventually as well

Calling the CloudFormation registry directly across multiple partitions with valid credentials doesn't seem practical, but registry type regional availability information might be obtainable through another data source

@iann0036
Copy link
Contributor Author

True-up'd with master.

Copy link
Contributor

@rjlohan rjlohan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one 🥇

@wbingli
Copy link
Contributor

wbingli commented Mar 21, 2020

Nice work!

Tested with one resource, docs looks great: https://gist.github.com/wbingli/f725de6f7950067b250f8f35a8c7ec09

@wbingli wbingli self-requested a review March 21, 2020 00:26
@wbingli wbingli merged commit c85cd03 into aws-cloudformation:master Mar 21, 2020
Comment on lines +70 to +76
{% if prop.createonly %}

_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement)
{% else %}

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
{% endif %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're generating documentation, it's probably worth noting nuance for something as important as potential resource replacement. For example, a version property might be upgradable in-place but not downgradable in-place, so neither of these definitions would be entirely accurate. My assumption would be while schema definitions like required and createOnlyProperties guarantee a property is required or requires resource replacement to update respectively, other properties not specified as required or createOnlyProperties may in fact be required or cause resource replacement under some circumstances. Immutability is important enough that its nuance should probably be captured in the resource schema itself in the future

Copy link
Contributor

@PatMyron PatMyron Mar 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BASIC_TYPE_MAPPINGS = {
"string": "String",
"number": "Double",
"integer": "Double",
Copy link
Contributor

@PatMyron PatMyron Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Integer, right?

Suggested change
"integer": "Double",
"integer": "Integer",

@staticmethod
def _get_docs_gettable_atts(docs_schema):
def _get_property_description(prop):
path = fragment_decode(prop, prefix="")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants